import {Buffer} from "buffer";
import {HdlcAddressPair} from "./HdlcAddressPair";
import {HdlcAddress} from "./HdlcAddress";
import {FcsCalc} from "./FcsCalc";
import {ReverseByteArrayInputStream} from "../ReverseByteArrayInputStream";
import {DataUtils} from "../DataUtils";


let CmdType = {
    CMD_RESERVE0: {value: 0, mask: 7},
    CMD_LINK: {value: 1, mask: 7},
    CMD_RESERVE2: {value: 2, mask: 7},
    CMD_USERDATA: {value: 3, mask: 7},
    CMD_RESERVE4: {value: 4, mask: 7},
    CMD_RESERVE5: {value: 5, mask: 7},
    CMD_RESERVE6: {value: 6, mask: 7},
    CMD_RESERVE7: {value: 7, mask: 7},
}

let FrameType = {
    CLI_RES: {value: 0x00, mask: 0xc0},
    CLI_REQ: {value: 0x40, mask: 0xc0},
    SRV_REQ: {value: 0x80, mask: 0xc0},
    SRV_RES: {value: 0xc0, mask: 0xc0},
    ERR_INVALID_TYPE : {value : 0xFF, mask : 0xFF},
}

function cmdTypeFor(controlByte: number): number {
    for (let cmdTypeKey in CmdType) {
        const data = CmdType[cmdTypeKey as keyof typeof CmdType];
        if ((data.mask & controlByte) == data.value) {
            return data.value;
        }
    }
    return CmdType.CMD_USERDATA.value;
}

function frameTypeFor(controlByte: number) : number{
    for (let frameTypeKey in FrameType) {
        const data = FrameType[frameTypeKey as keyof typeof FrameType];
        if ((data.mask & controlByte) == data.value) {
            return data.value;
        }
    }
    return FrameType.ERR_INVALID_TYPE.value;
}

export class HdlcFrame {

    FLAGBEG: number = 0x68;

    FLAGEND: number = 0x16;

    frameType: number = 0;

    controlField: number = 0;

    informationField: Buffer | null = null;

    segmented: boolean = false;

    addressPair: HdlcAddressPair | null = null;

    segframexh: number = 0;

    segframetype: number = 0;

    constructor() {
    }

    set_addpair_frametype(addressPair: HdlcAddressPair, frameType: number) {
        this.addressPair = addressPair;
        this.frameType = frameType;
    }

    set_all_param(frameType: number, informationField: Buffer, segmented: boolean,
                  controlField: number, addressPair: HdlcAddressPair, segframexh: number,
                  segframetype: number) {
        this.frameType = frameType;
        this.informationField = informationField;
        this.segmented = segmented;
        this.controlField = controlField;
        this.addressPair = addressPair;
        this.segframexh = segframexh;
        this.segframetype = segframetype;
    }

    static newCliReqFrame(addressPair: HdlcAddressPair, information: Buffer, segmented: boolean): HdlcFrame {
        let hdlcFrame = new HdlcFrame();
        hdlcFrame.set_addpair_frametype(addressPair, FrameType.CLI_REQ.value);
        hdlcFrame.informationField = information;
        hdlcFrame.segmented = segmented;
        hdlcFrame.controlField = CmdType.CMD_USERDATA.value;
        return hdlcFrame;
    }

    decode(input : ReverseByteArrayInputStream) : HdlcFrame{
        let fcsCalc = new FcsCalc();
        let segframexh = 0;
        let segframetype = 0;
        let codePosition = 0;
        let frameFormatH = input.read();
        if ((frameFormatH & 0xFF) != this.FLAGBEG) {
            throw new Error("Illegal frameformat");
        }
        let lenframe = (input.read() & 0xff);
        lenframe += (input.read() << 8) & 0xff00;
        let code = Buffer.alloc(lenframe);
        code.writeUInt8(lenframe, 0);
        code.writeUInt8((lenframe & 0xff00) >> 8, 1);
        let controlField = input.read();
        code.writeUInt8(controlField & 0xff, 2);
        codePosition += 3;
        fcsCalc.update(code, codePosition);

        // 获取控制码、帧
        let cmdType = cmdTypeFor(controlField & 0xFF);
        let frameType = frameTypeFor(controlField & 0xFF);
        let segmented = (0x20 & controlField) == 0x20;

        // 地址
        let srvaddr = HdlcFrame.decodeAddress(fcsCalc, input);
        console.log(srvaddr);

        let cliaddr = input.read();
        console.log(cliaddr);

        fcsCalc.updates(cliaddr);
        let tempbyte = input.read();

        console.log(tempbyte);

        fcsCalc.updates(tempbyte);
        tempbyte = input.read();

        console.log(tempbyte);

        fcsCalc.updates(tempbyte);
        fcsCalc.validateCurrentFcsValue();

        //
        let informationField : Buffer;
        let infoLength = lenframe - 8 - srvaddr.byteLength;
        console.log(infoLength);
        if (infoLength > 0) {
            if (segmented) {
                infoLength = infoLength - 2;
                let frameinfo = input.read();
                segframexh = frameinfo & 0x3FFF;
                segframetype = (frameinfo >> 14) & 0x03;
                let codetemp = Buffer.alloc(1000);
                codetemp.writeUInt8((frameinfo));
                fcsCalc.update(codetemp, 1);
            }

            informationField = Buffer.alloc(infoLength);

            for (let i = 0; i < infoLength; i++) {
                let data = input.read();
                console.log("informationField " + data);
                fcsCalc.updates(data);
                informationField[i] = data;
            }
            tempbyte = input.read();
            console.log(tempbyte);
            fcsCalc.updates(tempbyte);
            tempbyte = input.read();
            console.log(tempbyte);
            fcsCalc.updates(tempbyte);
            fcsCalc.validateCurrentFcsValue();

        } else {
            informationField = Buffer.alloc(0);
            tempbyte = input.read();
            fcsCalc.updates(tempbyte);
            tempbyte = input.read();
            fcsCalc.updates(tempbyte);
            fcsCalc.validateCurrentFcsValue();
        }
        tempbyte = input.read();
        console.log(tempbyte);
        let addressPair = new HdlcAddressPair(srvaddr, DataUtils.hexToBytes(cliaddr.toString(16)));
        let hdlcFrame = new HdlcFrame();

        hdlcFrame.set_all_param(frameType, informationField, segmented, cmdType,
            addressPair, segframexh, segframetype);
        return hdlcFrame;
    }

    static decodeAddress(fcsCalc : FcsCalc, input : ReverseByteArrayInputStream) : HdlcAddress{
        let data;
        let currentByte = input.read();
        let length = (currentByte & 0x0f) + 1;
        data = Buffer.alloc(length + 1);
        fcsCalc.updates(currentByte);
        data[0] = currentByte;
        let i = 0;
        while (i < length) {
            currentByte = input.read();
            fcsCalc.updates(currentByte);
            data[++i] = currentByte;
        }
        return HdlcAddress.decode(data);
    }

    encode(): Buffer {
        let data = this.encodeWithoutFlags();
        let temp = Buffer.alloc(data.length + 2);
        temp.writeUInt8(this.FLAGBEG, 0);
        data.copy(temp, 1, 0, data.length);
        temp.writeUInt8(this.FLAGEND, data.length + 1);
        return temp;
    }

    encodeWithoutFlags(): Buffer {
        if(this.informationField != null){
            let length = 8 + this.getSrvAddress().getByteLength();//9
            if (this.containsInformation()) {
                length += this.informationField.length;
            }
            if (this.isSegmented()) {
                length += 2;
            }
            console.log(length);
            let codePosition = 0;
            let code = Buffer.alloc(length);
            code.writeUInt8(length & 0x00ff, 0);
            code.writeUInt8((length & 0xff00) >> 8, 1);
            code.writeUInt8((this.controlField & 0x03) | this.frameType | (this.isSegmented() ? 0x20 : 0x0), 2);
            codePosition += 3;
            let srvEncode = this.getSrvAddress().encode();
            for (let i = 0; i < srvEncode.length; i++) {
                code.writeUInt8(srvEncode[i], i + codePosition);
            }
            codePosition += srvEncode.length;
            let cliEncode = this.getCliAddress();
            for (let i = 0; i < cliEncode.length; i++) {
                code.writeUInt8(cliEncode[i], i + codePosition);
            }
            codePosition += cliEncode.length;
            let fcsCalc = new FcsCalc();
            fcsCalc.update(code, codePosition);
            let hcsEncode = fcsCalc.fcsValueInBytes();
            for (let i = 0; i < hcsEncode.length; i++) {
                code.writeUInt8(hcsEncode[i], i + codePosition);
            }
            codePosition += hcsEncode.length;
            fcsCalc.update(fcsCalc.fcsValueInBytes(), fcsCalc.fcsValueInBytes().length);
            console.log("hcs is update");
            if (this.isSegmented()) {
                let segframeinfo = (this.segframexh & 0x3FFFF) | ((this.segframetype & 0x03) << 14);
                code.writeUInt8(segframeinfo & 0x00ff, codePosition + 1);
                code.writeUInt8((segframeinfo & 0xff00) >> 8, codePosition + 2);
                codePosition += 2;
                let segbyte = Buffer.alloc(2);
                code.copy(segbyte, 0, codePosition, codePosition + 2);
                fcsCalc.update(segbyte, 2);
            }
            if (this.containsInformation()) {
                for (let i = 0; i < this.informationField.length; i++) {
                    code.writeUInt8(this.informationField[i], i + codePosition);
                }
                codePosition += this.informationField.length;
                fcsCalc.update(this.informationField, this.informationField.length);
            }
            let fcs = fcsCalc.fcsValueInBytes();
            for (let i = 0; i < fcs.length; i++) {
                code.writeUInt8(fcs[i], i + codePosition);
            }
            return code;
        }else{
            throw new Error("apdu data is null");
        }
    }

    getSrvAddress(): HdlcAddress {
        if (this.addressPair != null) {
            return this.addressPair.getSrvAddr();
        } else {
            throw new Error("addressPair is null");
        }
    }

    getCliAddress(): Buffer {
        if (this.addressPair != null) {
            return this.addressPair.getCliAddr();
        } else {
            throw new Error("addressPair is null");
        }
    }

    getAddressPair(): HdlcAddressPair {
        if (this.addressPair != null) {
            return this.addressPair;
        } else {
            throw new Error("addressPair is null");
        }
    }

    getFrameType(): number {
        return this.frameType;
    }

    getInformationField(): Buffer {
        if (this.informationField != null) {
            return this.informationField;
        } else {
            throw new Error("apdu data is null");
        }
    }

    isSegmented(): boolean {
        return this.segmented;
    }

    containsInformation(): boolean {
        return this.informationField != null;
    }

    setSegFrameType(segframetype2: number) {
        this.segframetype = segframetype2;
    }

    setSegFrameXh(segframexh2: number) {
        this.segframexh = segframexh2;
    }

    getSegFrameType(): number {
        return this.segframetype;
    }

    getSegFrameXh(): number {
        return this.segframexh;
    }

}


